1 /*
2 * Copyright (C) 2009 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.common.collect;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkElementIndex;
21 import static com.google.common.base.Preconditions.checkNotNull;
22
23 import com.google.common.annotations.Beta;
24 import com.google.common.annotations.GwtCompatible;
25 import com.google.common.annotations.GwtIncompatible;
26 import com.google.common.base.Objects;
27
28 import java.io.Serializable;
29 import java.lang.reflect.Array;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 import javax.annotation.Nullable;
38
39 /**
40 * Fixed-size {@link Table} implementation backed by a two-dimensional array.
41 *
42 * <p>The allowed row and column keys must be supplied when the table is
43 * created. The table always contains a mapping for every row key / column pair.
44 * The value corresponding to a given row and column is null unless another
45 * value is provided.
46 *
47 * <p>The table's size is constant: the product of the number of supplied row
48 * keys and the number of supplied column keys. The {@code remove} and {@code
49 * clear} methods are not supported by the table or its views. The {@link
50 * #erase} and {@link #eraseAll} methods may be used instead.
51 *
52 * <p>The ordering of the row and column keys provided when the table is
53 * constructed determines the iteration ordering across rows and columns in the
54 * table's views. None of the view iterators support {@link Iterator#remove}.
55 * If the table is modified after an iterator is created, the iterator remains
56 * valid.
57 *
58 * <p>This class requires less memory than the {@link HashBasedTable} and {@link
59 * TreeBasedTable} implementations, except when the table is sparse.
60 *
61 * <p>Null row keys or column keys are not permitted.
62 *
63 * <p>This class provides methods involving the underlying array structure,
64 * where the array indices correspond to the position of a row or column in the
65 * lists of allowed keys and values. See the {@link #at}, {@link #set}, {@link
66 * #toArray}, {@link #rowKeyList}, and {@link #columnKeyList} methods for more
67 * details.
68 *
69 * <p>Note that this implementation is not synchronized. If multiple threads
70 * access the same cell of an {@code ArrayTable} concurrently and one of the
71 * threads modifies its value, there is no guarantee that the new value will be
72 * fully visible to the other threads. To guarantee that modifications are
73 * visible, synchronize access to the table. Unlike other {@code Table}
74 * implementations, synchronization is unnecessary between a thread that writes
75 * to one cell and a thread that reads from another.
76 *
77 * <p>See the Guava User Guide article on <a href=
78 * "http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Table">
79 * {@code Table}</a>.
80 *
81 * @author Jared Levy
82 * @since 10.0
83 */
84 @Beta
85 @GwtCompatible(emulated = true)
86 public final class ArrayTable<R, C, V> extends AbstractTable<R, C, V> implements Serializable {
87
88 /**
89 * Creates an empty {@code ArrayTable}.
90 *
91 * @param rowKeys row keys that may be stored in the generated table
92 * @param columnKeys column keys that may be stored in the generated table
93 * @throws NullPointerException if any of the provided keys is null
94 * @throws IllegalArgumentException if {@code rowKeys} or {@code columnKeys}
95 * contains duplicates or is empty
96 */
97 public static <R, C, V> ArrayTable<R, C, V> create(
98 Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys) {
99 return new ArrayTable<R, C, V>(rowKeys, columnKeys);
100 }
101
102 /*
103 * TODO(jlevy): Add factory methods taking an Enum class, instead of an
104 * iterable, to specify the allowed row keys and/or column keys. Note that
105 * custom serialization logic is needed to support different enum sizes during
106 * serialization and deserialization.
107 */
108
109 /**
110 * Creates an {@code ArrayTable} with the mappings in the provided table.
111 *
112 * <p>If {@code table} includes a mapping with row key {@code r} and a
113 * separate mapping with column key {@code c}, the returned table contains a
114 * mapping with row key {@code r} and column key {@code c}. If that row key /
115 * column key pair in not in {@code table}, the pair maps to {@code null} in
116 * the generated table.
117 *
118 * <p>The returned table allows subsequent {@code put} calls with the row keys
119 * in {@code table.rowKeySet()} and the column keys in {@code
120 * table.columnKeySet()}. Calling {@link #put} with other keys leads to an
121 * {@code IllegalArgumentException}.
122 *
123 * <p>The ordering of {@code table.rowKeySet()} and {@code
124 * table.columnKeySet()} determines the row and column iteration ordering of
125 * the returned table.
126 *
127 * @throws NullPointerException if {@code table} has a null key
128 * @throws IllegalArgumentException if the provided table is empty
129 */
130 public static <R, C, V> ArrayTable<R, C, V> create(Table<R, C, V> table) {
131 return (table instanceof ArrayTable<?, ?, ?>)
132 ? new ArrayTable<R, C, V>((ArrayTable<R, C, V>) table)
133 : new ArrayTable<R, C, V>(table);
134 }
135
136 private final ImmutableList<R> rowList;
137 private final ImmutableList<C> columnList;
138
139 // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex?
140 private final ImmutableMap<R, Integer> rowKeyToIndex;
141 private final ImmutableMap<C, Integer> columnKeyToIndex;
142 private final V[][] array;
143
144 private ArrayTable(Iterable<? extends R> rowKeys,
145 Iterable<? extends C> columnKeys) {
146 this.rowList = ImmutableList.copyOf(rowKeys);
147 this.columnList = ImmutableList.copyOf(columnKeys);
148 checkArgument(!rowList.isEmpty());
149 checkArgument(!columnList.isEmpty());
150
151 /*
152 * TODO(jlevy): Support empty rowKeys or columnKeys? If we do, when
153 * columnKeys is empty but rowKeys isn't, the table is empty but
154 * containsRow() can return true and rowKeySet() isn't empty.
155 */
156 rowKeyToIndex = index(rowList);
157 columnKeyToIndex = index(columnList);
158
159 @SuppressWarnings("unchecked")
160 V[][] tmpArray
161 = (V[][]) new Object[rowList.size()][columnList.size()];
162 array = tmpArray;
163 // Necessary because in GWT the arrays are initialized with "undefined" instead of null.
164 eraseAll();
165 }
166
167 private static <E> ImmutableMap<E, Integer> index(List<E> list) {
168 ImmutableMap.Builder<E, Integer> columnBuilder = ImmutableMap.builder();
169 for (int i = 0; i < list.size(); i++) {
170 columnBuilder.put(list.get(i), i);
171 }
172 return columnBuilder.build();
173 }
174
175 private ArrayTable(Table<R, C, V> table) {
176 this(table.rowKeySet(), table.columnKeySet());
177 putAll(table);
178 }
179
180 private ArrayTable(ArrayTable<R, C, V> table) {
181 rowList = table.rowList;
182 columnList = table.columnList;
183 rowKeyToIndex = table.rowKeyToIndex;
184 columnKeyToIndex = table.columnKeyToIndex;
185 @SuppressWarnings("unchecked")
186 V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()];
187 array = copy;
188 // Necessary because in GWT the arrays are initialized with "undefined" instead of null.
189 eraseAll();
190 for (int i = 0; i < rowList.size(); i++) {
191 System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length);
192 }
193 }
194
195 private abstract static class ArrayMap<K, V> extends Maps.ImprovedAbstractMap<K, V> {
196 private final ImmutableMap<K, Integer> keyIndex;
197
198 private ArrayMap(ImmutableMap<K, Integer> keyIndex) {
199 this.keyIndex = keyIndex;
200 }
201
202 @Override
203 public Set<K> keySet() {
204 return keyIndex.keySet();
205 }
206
207 K getKey(int index) {
208 return keyIndex.keySet().asList().get(index);
209 }
210
211 abstract String getKeyRole();
212
213 @Nullable abstract V getValue(int index);
214
215 @Nullable abstract V setValue(int index, V newValue);
216
217 @Override
218 public int size() {
219 return keyIndex.size();
220 }
221
222 @Override
223 public boolean isEmpty() {
224 return keyIndex.isEmpty();
225 }
226
227 @Override
228 protected Set<Entry<K, V>> createEntrySet() {
229 return new Maps.EntrySet<K, V>() {
230 @Override
231 Map<K, V> map() {
232 return ArrayMap.this;
233 }
234
235 @Override
236 public Iterator<Entry<K, V>> iterator() {
237 return new AbstractIndexedListIterator<Entry<K, V>>(size()) {
238 @Override
239 protected Entry<K, V> get(final int index) {
240 return new AbstractMapEntry<K, V>() {
241 @Override
242 public K getKey() {
243 return ArrayMap.this.getKey(index);
244 }
245
246 @Override
247 public V getValue() {
248 return ArrayMap.this.getValue(index);
249 }
250
251 @Override
252 public V setValue(V value) {
253 return ArrayMap.this.setValue(index, value);
254 }
255 };
256 }
257 };
258 }
259 };
260 }
261
262 // TODO(user): consider an optimized values() implementation
263
264 @Override
265 public boolean containsKey(@Nullable Object key) {
266 return keyIndex.containsKey(key);
267 }
268
269 @Override
270 public V get(@Nullable Object key) {
271 Integer index = keyIndex.get(key);
272 if (index == null) {
273 return null;
274 } else {
275 return getValue(index);
276 }
277 }
278
279 @Override
280 public V put(K key, V value) {
281 Integer index = keyIndex.get(key);
282 if (index == null) {
283 throw new IllegalArgumentException(
284 getKeyRole() + " " + key + " not in " + keyIndex.keySet());
285 }
286 return setValue(index, value);
287 }
288
289 @Override
290 public V remove(Object key) {
291 throw new UnsupportedOperationException();
292 }
293
294 @Override
295 public void clear() {
296 throw new UnsupportedOperationException();
297 }
298 }
299
300 /**
301 * Returns, as an immutable list, the row keys provided when the table was
302 * constructed, including those that are mapped to null values only.
303 */
304 public ImmutableList<R> rowKeyList() {
305 return rowList;
306 }
307
308 /**
309 * Returns, as an immutable list, the column keys provided when the table was
310 * constructed, including those that are mapped to null values only.
311 */
312 public ImmutableList<C> columnKeyList() {
313 return columnList;
314 }
315
316 /**
317 * Returns the value corresponding to the specified row and column indices.
318 * The same value is returned by {@code
319 * get(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex))}, but
320 * this method runs more quickly.
321 *
322 * @param rowIndex position of the row key in {@link #rowKeyList()}
323 * @param columnIndex position of the row key in {@link #columnKeyList()}
324 * @return the value with the specified row and column
325 * @throws IndexOutOfBoundsException if either index is negative, {@code
326 * rowIndex} is greater then or equal to the number of allowed row keys,
327 * or {@code columnIndex} is greater then or equal to the number of
328 * allowed column keys
329 */
330 public V at(int rowIndex, int columnIndex) {
331 // In GWT array access never throws IndexOutOfBoundsException.
332 checkElementIndex(rowIndex, rowList.size());
333 checkElementIndex(columnIndex, columnList.size());
334 return array[rowIndex][columnIndex];
335 }
336
337 /**
338 * Associates {@code value} with the specified row and column indices. The
339 * logic {@code
340 * put(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex), value)}
341 * has the same behavior, but this method runs more quickly.
342 *
343 * @param rowIndex position of the row key in {@link #rowKeyList()}
344 * @param columnIndex position of the row key in {@link #columnKeyList()}
345 * @param value value to store in the table
346 * @return the previous value with the specified row and column
347 * @throws IndexOutOfBoundsException if either index is negative, {@code
348 * rowIndex} is greater then or equal to the number of allowed row keys,
349 * or {@code columnIndex} is greater then or equal to the number of
350 * allowed column keys
351 */
352 public V set(int rowIndex, int columnIndex, @Nullable V value) {
353 // In GWT array access never throws IndexOutOfBoundsException.
354 checkElementIndex(rowIndex, rowList.size());
355 checkElementIndex(columnIndex, columnList.size());
356 V oldValue = array[rowIndex][columnIndex];
357 array[rowIndex][columnIndex] = value;
358 return oldValue;
359 }
360
361 /**
362 * Returns a two-dimensional array with the table contents. The row and column
363 * indices correspond to the positions of the row and column in the iterables
364 * provided during table construction. If the table lacks a mapping for a
365 * given row and column, the corresponding array element is null.
366 *
367 * <p>Subsequent table changes will not modify the array, and vice versa.
368 *
369 * @param valueClass class of values stored in the returned array
370 */
371 @GwtIncompatible("reflection")
372 public V[][] toArray(Class<V> valueClass) {
373 // Can change to use varargs in JDK 1.6 if we want
374 @SuppressWarnings("unchecked") // TODO: safe?
375 V[][] copy = (V[][]) Array.newInstance(
376 valueClass, new int[] { rowList.size(), columnList.size() });
377 for (int i = 0; i < rowList.size(); i++) {
378 System.arraycopy(array[i], 0, copy[i], 0, array[i].length);
379 }
380 return copy;
381 }
382
383 /**
384 * Not supported. Use {@link #eraseAll} instead.
385 *
386 * @throws UnsupportedOperationException always
387 * @deprecated Use {@link #eraseAll}
388 */
389 @Override
390 @Deprecated public void clear() {
391 throw new UnsupportedOperationException();
392 }
393
394 /**
395 * Associates the value {@code null} with every pair of allowed row and column
396 * keys.
397 */
398 public void eraseAll() {
399 for (V[] row : array) {
400 Arrays.fill(row, null);
401 }
402 }
403
404 /**
405 * Returns {@code true} if the provided keys are among the keys provided when
406 * the table was constructed.
407 */
408 @Override
409 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) {
410 return containsRow(rowKey) && containsColumn(columnKey);
411 }
412
413 /**
414 * Returns {@code true} if the provided column key is among the column keys
415 * provided when the table was constructed.
416 */
417 @Override
418 public boolean containsColumn(@Nullable Object columnKey) {
419 return columnKeyToIndex.containsKey(columnKey);
420 }
421
422 /**
423 * Returns {@code true} if the provided row key is among the row keys
424 * provided when the table was constructed.
425 */
426 @Override
427 public boolean containsRow(@Nullable Object rowKey) {
428 return rowKeyToIndex.containsKey(rowKey);
429 }
430
431 @Override
432 public boolean containsValue(@Nullable Object value) {
433 for (V[] row : array) {
434 for (V element : row) {
435 if (Objects.equal(value, element)) {
436 return true;
437 }
438 }
439 }
440 return false;
441 }
442
443 @Override
444 public V get(@Nullable Object rowKey, @Nullable Object columnKey) {
445 Integer rowIndex = rowKeyToIndex.get(rowKey);
446 Integer columnIndex = columnKeyToIndex.get(columnKey);
447 return (rowIndex == null || columnIndex == null)
448 ? null : at(rowIndex, columnIndex);
449 }
450
451 /**
452 * Always returns {@code false}.
453 */
454 @Override
455 public boolean isEmpty() {
456 return false;
457 }
458
459 /**
460 * {@inheritDoc}
461 *
462 * @throws IllegalArgumentException if {@code rowKey} is not in {@link
463 * #rowKeySet()} or {@code columnKey} is not in {@link #columnKeySet()}.
464 */
465 @Override
466 public V put(R rowKey, C columnKey, @Nullable V value) {
467 checkNotNull(rowKey);
468 checkNotNull(columnKey);
469 Integer rowIndex = rowKeyToIndex.get(rowKey);
470 checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList);
471 Integer columnIndex = columnKeyToIndex.get(columnKey);
472 checkArgument(columnIndex != null,
473 "Column %s not in %s", columnKey, columnList);
474 return set(rowIndex, columnIndex, value);
475 }
476
477 /*
478 * TODO(jlevy): Consider creating a merge() method, similar to putAll() but
479 * copying non-null values only.
480 */
481
482 /**
483 * {@inheritDoc}
484 *
485 * <p>If {@code table} is an {@code ArrayTable}, its null values will be
486 * stored in this table, possibly replacing values that were previously
487 * non-null.
488 *
489 * @throws NullPointerException if {@code table} has a null key
490 * @throws IllegalArgumentException if any of the provided table's row keys or
491 * column keys is not in {@link #rowKeySet()} or {@link #columnKeySet()}
492 */
493 @Override
494 public void putAll(Table<? extends R, ? extends C, ? extends V> table) {
495 super.putAll(table);
496 }
497
498 /**
499 * Not supported. Use {@link #erase} instead.
500 *
501 * @throws UnsupportedOperationException always
502 * @deprecated Use {@link #erase}
503 */
504 @Override
505 @Deprecated public V remove(Object rowKey, Object columnKey) {
506 throw new UnsupportedOperationException();
507 }
508
509 /**
510 * Associates the value {@code null} with the specified keys, assuming both
511 * keys are valid. If either key is null or isn't among the keys provided
512 * during construction, this method has no effect.
513 *
514 * <p>This method is equivalent to {@code put(rowKey, columnKey, null)} when
515 * both provided keys are valid.
516 *
517 * @param rowKey row key of mapping to be erased
518 * @param columnKey column key of mapping to be erased
519 * @return the value previously associated with the keys, or {@code null} if
520 * no mapping existed for the keys
521 */
522 public V erase(@Nullable Object rowKey, @Nullable Object columnKey) {
523 Integer rowIndex = rowKeyToIndex.get(rowKey);
524 Integer columnIndex = columnKeyToIndex.get(columnKey);
525 if (rowIndex == null || columnIndex == null) {
526 return null;
527 }
528 return set(rowIndex, columnIndex, null);
529 }
530
531 // TODO(jlevy): Add eraseRow and eraseColumn methods?
532
533 @Override
534 public int size() {
535 return rowList.size() * columnList.size();
536 }
537
538 /**
539 * Returns an unmodifiable set of all row key / column key / value
540 * triplets. Changes to the table will update the returned set.
541 *
542 * <p>The returned set's iterator traverses the mappings with the first row
543 * key, the mappings with the second row key, and so on.
544 *
545 * <p>The value in the returned cells may change if the table subsequently
546 * changes.
547 *
548 * @return set of table cells consisting of row key / column key / value
549 * triplets
550 */
551 @Override
552 public Set<Cell<R, C, V>> cellSet() {
553 return super.cellSet();
554 }
555
556 @Override
557 Iterator<Cell<R, C, V>> cellIterator() {
558 return new AbstractIndexedListIterator<Cell<R, C, V>>(size()) {
559 @Override protected Cell<R, C, V> get(final int index) {
560 return new Tables.AbstractCell<R, C, V>() {
561 final int rowIndex = index / columnList.size();
562 final int columnIndex = index % columnList.size();
563 @Override
564 public R getRowKey() {
565 return rowList.get(rowIndex);
566 }
567 @Override
568 public C getColumnKey() {
569 return columnList.get(columnIndex);
570 }
571 @Override
572 public V getValue() {
573 return at(rowIndex, columnIndex);
574 }
575 };
576 }
577 };
578 }
579
580 /**
581 * Returns a view of all mappings that have the given column key. If the
582 * column key isn't in {@link #columnKeySet()}, an empty immutable map is
583 * returned.
584 *
585 * <p>Otherwise, for each row key in {@link #rowKeySet()}, the returned map
586 * associates the row key with the corresponding value in the table. Changes
587 * to the returned map will update the underlying table, and vice versa.
588 *
589 * @param columnKey key of column to search for in the table
590 * @return the corresponding map from row keys to values
591 */
592 @Override
593 public Map<R, V> column(C columnKey) {
594 checkNotNull(columnKey);
595 Integer columnIndex = columnKeyToIndex.get(columnKey);
596 return (columnIndex == null)
597 ? ImmutableMap.<R, V>of() : new Column(columnIndex);
598 }
599
600 private class Column extends ArrayMap<R, V> {
601 final int columnIndex;
602
603 Column(int columnIndex) {
604 super(rowKeyToIndex);
605 this.columnIndex = columnIndex;
606 }
607
608 @Override
609 String getKeyRole() {
610 return "Row";
611 }
612
613 @Override
614 V getValue(int index) {
615 return at(index, columnIndex);
616 }
617
618 @Override
619 V setValue(int index, V newValue) {
620 return set(index, columnIndex, newValue);
621 }
622 }
623
624 /**
625 * Returns an immutable set of the valid column keys, including those that
626 * are associated with null values only.
627 *
628 * @return immutable set of column keys
629 */
630 @Override
631 public ImmutableSet<C> columnKeySet() {
632 return columnKeyToIndex.keySet();
633 }
634
635 private transient ColumnMap columnMap;
636
637 @Override
638 public Map<C, Map<R, V>> columnMap() {
639 ColumnMap map = columnMap;
640 return (map == null) ? columnMap = new ColumnMap() : map;
641 }
642
643 private class ColumnMap extends ArrayMap<C, Map<R, V>> {
644 private ColumnMap() {
645 super(columnKeyToIndex);
646 }
647
648 @Override
649 String getKeyRole() {
650 return "Column";
651 }
652
653 @Override
654 Map<R, V> getValue(int index) {
655 return new Column(index);
656 }
657
658 @Override
659 Map<R, V> setValue(int index, Map<R, V> newValue) {
660 throw new UnsupportedOperationException();
661 }
662
663 @Override
664 public Map<R, V> put(C key, Map<R, V> value) {
665 throw new UnsupportedOperationException();
666 }
667 }
668
669 /**
670 * Returns a view of all mappings that have the given row key. If the
671 * row key isn't in {@link #rowKeySet()}, an empty immutable map is
672 * returned.
673 *
674 * <p>Otherwise, for each column key in {@link #columnKeySet()}, the returned
675 * map associates the column key with the corresponding value in the
676 * table. Changes to the returned map will update the underlying table, and
677 * vice versa.
678 *
679 * @param rowKey key of row to search for in the table
680 * @return the corresponding map from column keys to values
681 */
682 @Override
683 public Map<C, V> row(R rowKey) {
684 checkNotNull(rowKey);
685 Integer rowIndex = rowKeyToIndex.get(rowKey);
686 return (rowIndex == null) ? ImmutableMap.<C, V>of() : new Row(rowIndex);
687 }
688
689 private class Row extends ArrayMap<C, V> {
690 final int rowIndex;
691
692 Row(int rowIndex) {
693 super(columnKeyToIndex);
694 this.rowIndex = rowIndex;
695 }
696
697 @Override
698 String getKeyRole() {
699 return "Column";
700 }
701
702 @Override
703 V getValue(int index) {
704 return at(rowIndex, index);
705 }
706
707 @Override
708 V setValue(int index, V newValue) {
709 return set(rowIndex, index, newValue);
710 }
711 }
712
713 /**
714 * Returns an immutable set of the valid row keys, including those that are
715 * associated with null values only.
716 *
717 * @return immutable set of row keys
718 */
719 @Override
720 public ImmutableSet<R> rowKeySet() {
721 return rowKeyToIndex.keySet();
722 }
723
724 private transient RowMap rowMap;
725
726 @Override
727 public Map<R, Map<C, V>> rowMap() {
728 RowMap map = rowMap;
729 return (map == null) ? rowMap = new RowMap() : map;
730 }
731
732 private class RowMap extends ArrayMap<R, Map<C, V>> {
733 private RowMap() {
734 super(rowKeyToIndex);
735 }
736
737 @Override
738 String getKeyRole() {
739 return "Row";
740 }
741
742 @Override
743 Map<C, V> getValue(int index) {
744 return new Row(index);
745 }
746
747 @Override
748 Map<C, V> setValue(int index, Map<C, V> newValue) {
749 throw new UnsupportedOperationException();
750 }
751
752 @Override
753 public Map<C, V> put(R key, Map<C, V> value) {
754 throw new UnsupportedOperationException();
755 }
756 }
757
758 /**
759 * Returns an unmodifiable collection of all values, which may contain
760 * duplicates. Changes to the table will update the returned collection.
761 *
762 * <p>The returned collection's iterator traverses the values of the first row
763 * key, the values of the second row key, and so on.
764 *
765 * @return collection of values
766 */
767 @Override
768 public Collection<V> values() {
769 return super.values();
770 }
771
772 private static final long serialVersionUID = 0;
773 }